package com.gitee.wsl.time.wheel

import com.gitee.wsl.time.timeout.HashedWheelTimer
import com.gitee.wsl.time.timeout.Timeout
import com.gitee.wsl.time.timeout.TimerTask
import junit.framework.TestCase.assertFalse

import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.TimeUnit
import kotlin.test.DefaultAsserter.assertTrue
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail


class HashedWheelTimerTest {
    @Test
    fun testnormalizeTicksPerWheel() {
        for (i in 0..999) {
            val num = ThreadLocalRandom.current().nextInt(0, 100)
            assertEquals(normalizeTicksPerWheel0(num), normalizeTicksPerWheel1(num))
        }

        for (i in 0..999) {
            val num = ThreadLocalRandom.current().nextInt(100, 1000)
            assertEquals(normalizeTicksPerWheel0(num), normalizeTicksPerWheel1(num))
        }

        for (i in 0..999) {
            val num = ThreadLocalRandom.current().nextInt(100000, 999999999)
            assertEquals(normalizeTicksPerWheel0(num), normalizeTicksPerWheel1(num))
        }
    }

    private fun normalizeTicksPerWheel0(ticksPerWheel: Int): Int {
        var normalizedTicksPerWheel = 1
        while (normalizedTicksPerWheel < ticksPerWheel) {
            normalizedTicksPerWheel = normalizedTicksPerWheel shl 1
        }
        return normalizedTicksPerWheel
    }

    private fun normalizeTicksPerWheel1(ticksPerWheel: Int): Int {
        // Fixed calculation process to avoid multi-cycle inefficiency
        var n = ticksPerWheel - 1
        n = n or (n ushr 1)
        n = n or (n ushr 2)
        n = n or (n ushr 4)
        n = n or (n ushr 8)
        n = n or (n ushr 16)
        // Prevent spillage, 1073741824 = 2^30
        return if (n < 0) 1 else if (n >= 1073741824) 1073741824 else n + 1
    }

    @Test
    @Throws(InterruptedException::class)
    fun testScheduleTimeoutShouldNotRunBeforeDelay() {
        val timer = HashedWheelTimer()
        val barrier= CountDownLatch(1)
        val timeout: Timeout = timer.newTimeout(object : TimerTask {
            @Throws(Exception::class)
            public override fun run(timeout: Timeout) {
                fail("This should not have run")
                //barrier.countDown()
            }
        }, 10, TimeUnit.SECONDS)
        assertFalse(barrier.await(3, TimeUnit.SECONDS))
        assertFalse("timer should not expire", timeout.isExpired)
        timer.stop()
    }

    @Test
    @Throws(InterruptedException::class)
    fun testScheduleTimeoutShouldRunAfterDelay() {
        val timer= HashedWheelTimer()
        val barrier: CountDownLatch = CountDownLatch(1)
        val timeout: Timeout = timer.newTimeout(object : TimerTask {
            @Throws(Exception::class)
            public override fun run(timeout: Timeout) {
                barrier.countDown()
            }
        }, 2, TimeUnit.SECONDS)
        assertTrue(barrier.await(3, TimeUnit.SECONDS))
        assertTrue("timer should expire", timeout.isExpired)
        timer.stop()
    }

    @Test(timeout = 3000L)
    @Throws(InterruptedException::class)
    fun testStopTimer() {
        val latch: CountDownLatch = CountDownLatch(3)
        val timerProcessed= HashedWheelTimer()
        for (i in 0..2) {
            timerProcessed.newTimeout(object : TimerTask {
                @Throws(Exception::class)
                public override fun run(timeout: Timeout) {
                    latch.countDown()
                }
            }, 1, TimeUnit.MILLISECONDS)
        }

        latch.await()
        assertEquals( 0, timerProcessed.stop().size,"Number of unprocessed timeouts should be 0")

        val timerUnprocessed = HashedWheelTimer()
        for (i in 0..4) {
            timerUnprocessed.newTimeout(object : TimerTask {
                @Throws(Exception::class)
                public override fun run(timeout: Timeout) {
                }
            }, 5, TimeUnit.SECONDS)
        }
        Thread.sleep(1000L) // sleep for a second
        assertFalse("Number of unprocessed timeouts should be greater than 0", timerUnprocessed.stop().isEmpty())
    }

    @Test(timeout = 3000L)
    @Throws(InterruptedException::class)
    fun testTimerShouldThrowExceptionAfterShutdownForNewTimeouts() {
        val latch: CountDownLatch = CountDownLatch(3)
        val timer = HashedWheelTimer()
        for (i in 0..2) {
            timer.newTimeout(object : TimerTask {
                @Throws(Exception::class)
                public override fun run(timeout: Timeout) {
                    latch.countDown()
                }
            }, 1, TimeUnit.MILLISECONDS)
        }

        latch.await()
        timer.stop()

        try {
            timer.newTimeout(createNoOpTimerTask(), 1, TimeUnit.MILLISECONDS)
            fail("Expected exception didn't occur.")
        } catch (ignored: IllegalStateException) {
            // expected
        }
    }

    @Test(timeout = 5000L)
    @Throws(InterruptedException::class)
    fun testTimerOverflowWheelLength() {
        val timer = HashedWheelTimer(
            Executors.defaultThreadFactory(), 100, TimeUnit.MILLISECONDS, 32
        )
        val latch: CountDownLatch = CountDownLatch(3)

        timer.newTimeout(object : TimerTask {
            @Throws(Exception::class)
            public override fun run(timeout: Timeout) {
                timer.newTimeout(this, 100, TimeUnit.MILLISECONDS)
                latch.countDown()
            }
        }, 100, TimeUnit.MILLISECONDS)

        latch.await()
        assertFalse(timer.stop().isEmpty())
    }

    @Test
    @Throws(InterruptedException::class)
    fun testExecutionOnTime() {
        val tickDuration = 200
        val timeout = 125
        val maxTimeout = 2 * (tickDuration + timeout)
        val timer = HashedWheelTimer(tickDuration.toLong(), TimeUnit.MILLISECONDS)
        val queue = LinkedBlockingQueue<Long?>()

        val scheduledTasks = 100000
        for (i in 0..<scheduledTasks) {
            val start = System.nanoTime()
            timer.newTimeout(object : TimerTask {
                @Throws(Exception::class)
                public override fun run(timeout: Timeout) {
                    queue.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start))
                }
            }, timeout.toLong(), TimeUnit.MILLISECONDS)
        }

        for (i in 0..<scheduledTasks) {
            val delay = queue.take()
            if (delay != null) {
                assertTrue("Timeout + $scheduledTasks delay $delay must be $timeout < $maxTimeout",delay >= timeout && delay < maxTimeout)
            }
        }

        timer.stop()
    }

    @Test
    @Throws(InterruptedException::class)
    fun testExecutionOnTaskExecutor() {
        val timeout = 10

        val timeoutLatch: CountDownLatch = CountDownLatch(1)
        val timer = HashedWheelTimer(
            Executors.defaultThreadFactory(), 100,
            TimeUnit.MILLISECONDS, 32
        )
        timer.newTimeout(
            TimerTask { timeout1: Timeout? -> timeoutLatch.countDown() },
            timeout.toLong(),
            TimeUnit.MILLISECONDS
        )

        timeoutLatch.await()
        timer.stop()
    }

    @Test
    fun testRejectedExecutionExceptionWhenTooManyTimeoutsAreAddedBackToBack() {
        val timer = HashedWheelTimer(
            Executors.defaultThreadFactory(), 100,
            TimeUnit.MILLISECONDS, 32, 2
        )
        timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS)
        timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS)
        try {
            timer.newTimeout(createNoOpTimerTask(), 1, TimeUnit.MILLISECONDS)
            fail("Timer allowed adding 3 timeouts when maxPendingTimeouts was 2")
        } catch (e: RejectedExecutionException) {
            // Expected
        } finally {
            timer.stop()
        }
    }

    @Test
    @Throws(InterruptedException::class)
    fun testNewTimeoutShouldStopThrowingRejectedExecutionExceptionWhenExistingTimeoutIsCancelled() {
        val tickDurationMs = 100
        val timer = HashedWheelTimer(
            Executors.defaultThreadFactory(), tickDurationMs.toLong(),
            TimeUnit.MILLISECONDS, 32
        )
        timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS)
        val timeoutToCancel: Timeout = timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS)
        assertTrue(timeoutToCancel.cancel())

        Thread.sleep((tickDurationMs * 5).toLong())

        val secondLatch: CountDownLatch = CountDownLatch(1)
        timer.newTimeout(createCountDownLatchTimerTask(secondLatch), 90, TimeUnit.MILLISECONDS)

        secondLatch.await()
        timer.stop()
    }

    @Test(timeout = 3000L)
    @Throws(InterruptedException::class)
    fun testNewTimeoutShouldStopThrowingRejectedExecutionExceptionWhenExistingTimeoutIsExecuted() {
        val latch: CountDownLatch = CountDownLatch(1)
        val timer = HashedWheelTimer(
            Executors.defaultThreadFactory(), 25,
            TimeUnit.MILLISECONDS, 4
        )
        timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS)
        timer.newTimeout(createCountDownLatchTimerTask(latch), 90, TimeUnit.MILLISECONDS)

        latch.await()

        val secondLatch: CountDownLatch = CountDownLatch(1)
        timer.newTimeout(createCountDownLatchTimerTask(secondLatch), 90, TimeUnit.MILLISECONDS)

        secondLatch.await()
        timer.stop()
    }

    @Test
    @Throws(InterruptedException::class)
    fun reportPendingTimeouts() {
        val latch: CountDownLatch = CountDownLatch(1)
        val timer = HashedWheelTimer()
        val t1: Timeout = timer.newTimeout(createNoOpTimerTask(), 100, TimeUnit.MINUTES)
        val t2: Timeout = timer.newTimeout(createNoOpTimerTask(), 100, TimeUnit.MINUTES)
        timer.newTimeout(createCountDownLatchTimerTask(latch), 90, TimeUnit.MILLISECONDS)

        assertEquals(3, timer.pendingTimeouts())
        t1.cancel()
        t2.cancel()
        latch.await()

        assertEquals(0, timer.pendingTimeouts())
        timer.stop()
    }

    @Test
    @Throws(InterruptedException::class)
    fun testOverflow() {
        val timer = HashedWheelTimer()
        val latch: CountDownLatch = CountDownLatch(1)
        val timeout: Timeout = timer.newTimeout(
            TimerTask { timeout1: Timeout? -> latch.countDown() },
            Long.Companion.MAX_VALUE,
            TimeUnit.MILLISECONDS
        )
        assertFalse(latch.await(1, TimeUnit.SECONDS))
        timeout.cancel()
        timer.stop()
    }

    companion object {
        private fun createNoOpTimerTask(): TimerTask {
            return TimerTask { timeout -> }
        }

        private fun createCountDownLatchTimerTask(latch: CountDownLatch): TimerTask {
            return TimerTask { timeout -> latch.countDown() }
        }
    }
}
